/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import 'dart:math';
import 'package:cbor/cbor.dart' as cbor;               //  CBOR Encoder and Decoder. From https://pub.dev/packages/cbor
import 'package:typed_data/typed_data.dart' as typed;  //  Helpers for Byte Buffers. From https://pub.dev/packages/typed_data

////////////////////////////////////////
//  nmxact/nmp/nmp.go
//  Converted from Go: https://github.com/lupyuen/mynewt-newtmgr/blob/master/nmxact/nmp/nmp.go

const NMP_HDR_SIZE = 8;

/// SMP Header
class NmpHdr {
  int Op;    //  uint8: 3 bits of opcode
  int Flags; //  uint8
  int Len;   //  uint16
  int Group; //  uint16
  int Seq;   //  uint8
  int Id;    //  uint8
  
  /// Construct an SMP Header
  NmpHdr(
    this.Op,    //  uint8: 3 bits of opcode
    this.Flags, //  uint8
    this.Len,   //  uint16
    this.Group, //  uint16
    this.Seq,   //  uint8
    this.Id     //  uint8
  );
  
  /// Return this SMP Header as a list of bytes
  typed.Uint8Buffer Bytes() {  //  Returns []byte
    var buf = typed.Uint8Buffer();  //  make([]byte, 0, NMP_HDR_SIZE);
    
    buf.add(this.Op);
    buf.add(this.Flags);

    typed.Uint8Buffer u16b = binaryBigEndianPutUint16(this.Len);
    buf.addAll(u16b);

    u16b = binaryBigEndianPutUint16(this.Group);
    buf.addAll(u16b);

    buf.add(this.Seq);
    buf.add(this.Id);
    assert(buf.length == NMP_HDR_SIZE);

    return buf;
  }  
}

/// SMP Message
class NmpMsg {
  NmpHdr Hdr;
  NmpReq Body;  //  Previously interface{}

  /// Construct an SMP Message
  NmpMsg(this.Hdr, this.Body);
}

/// SMP Request Message
abstract class NmpReq {
  NmpHdr Hdr();
  void SetHdr(NmpHdr hdr);

  NmpMsg Msg();
  void Encode(cbor.MapBuilder builder);
}

/// SMP Response Message
abstract class NmpRsp {
  NmpHdr Hdr();
  void SetHdr(NmpHdr msg);

  NmpMsg Msg();
}

/// SMP Base Message
mixin NmpBase {
  NmpHdr hdr;  //  Will not be encoded: `codec:"-"`
  
  NmpHdr Hdr() {
    return hdr;
  }
  
  void SetHdr(NmpHdr h) {
    hdr = h;
  }
}

NmpMsg MsgFromReq(NmpReq r) {
  return NmpMsg(
    r.Hdr(),
    r
  );
}

NmpMsg NewNmpMsg() {
  return NmpMsg(
    NmpHdr(0, 0, 0, 0, 0, 0),
    null
  );
}

NmpHdr DecodeNmpHdr(typed.Uint8Buffer data /* []byte */) {
  if (data.length < NMP_HDR_SIZE) {
    throw Exception(
      "Newtmgr request buffer too small ${data.length} bytes"
    );
  }

  final hdr = NmpHdr(
    data[0],  //  Op:    uint8
    data[1],  //  Flags: uint8
    binaryBigEndianUint16(data[2], data[3]),  //  Len: binary.BigEndian.Uint16
    binaryBigEndianUint16(data[4], data[5]),  //  Group: binary.BigEndian.Uint16
    data[6],  //  Seq:   uint8
    data[7],  //  Id:    uint8       
  );

  return hdr;
}

/// Encode SMP Request Body with CBOR and return the byte array
typed.Uint8Buffer BodyBytes(  //  Returns []byte
  NmpReq body  //  Previously interface{}
) {
  // Get our cbor instance, always do this, it correctly initialises the decoder.
  final inst = cbor.Cbor();

  // Get our encoder and map builder
  final encoder = inst.encoder;
  final mapBuilder = cbor.MapBuilder.builder();

  //  Encode the body as a CBOR map
  body.Encode(mapBuilder);
  final mapData = mapBuilder.getData();
  encoder.addBuilderOutput(mapData);

  //  Get the encoded body
  final data = inst.output.getData();

  //  Decode the encoded body and pretty print it
  inst.decodeFromInput();  //  print(inst.decodedPrettyPrint(false));
  final hdr = body.Hdr();
  print(
    "Encoded {NmpBase:{hdr:{"
    "Op:${ hdr.Op } "
    "Flags:${ hdr.Flags } "
    "Len:${ hdr.Len } "
    "Group:${ hdr.Group } "
    "Seq:${ hdr.Seq } "
    "Id:${ hdr.Id }}}} "
    "${ inst.decodedToJSON() } "
    "to:\n${ hexDump(data) }"
  );
  return data;
}

/// Encode the SMP Message with CBOR and return the byte array
typed.Uint8Buffer EncodeNmpPlain(NmpMsg nmr) {  //  Returns []byte
  final bb = BodyBytes(nmr.Body);

  nmr.Hdr.Len = bb.length;  //  uint16

  final hb = nmr.Hdr.Bytes();
  var data = typed.Uint8Buffer();
  data.addAll(hb);
  data.addAll(bb);

  print("Encoded:\n${ hexDump(data) }");
  return data;
}

/// Init the SMP Request and set the sequence number
void fillNmpReqWithSeq(
  NmpReq req,
  int op,     //  uint8
  int group,  //  uint16
  int id,     //  uint8
  int seq     //  uint8
) {
  final hdr = NmpHdr(
    op,     //  Op
    0,      //  Flags
    0,      //  Len
    group,  //  Group
    seq,    //  Seq
    id      //  Id
  );

  req.SetHdr(hdr);
}

/// Init the SMP Request and set the next sequence number
void fillNmpReq(
  NmpReq req, 
  int op,     //  uint8
  int group,  //  uint16
  int id      //  uint8
) {
  fillNmpReqWithSeq(
    req, 
    op, 
    group, 
    id, 
    NextNmpSeq()  //  From nmxutil
  );
}

/// Return byte array [a,b] as unsigned 16-bit int
int binaryBigEndianUint16(int a, int b) {
  return (a << 8) + b;
}

/// Return unsigned int u as big endian byte array
typed.Uint8Buffer binaryBigEndianPutUint16(int u) {
  var data = typed.Uint8Buffer();
  data.add(u >> 8);
  data.add(u & 0xff);
  return data;
}

/// Return the buffer buf dumped as hex numbers
String hexDump(typed.Uint8Buffer buf) {
  return buf.map(
    (b) {
      return b.toRadixString(16).padLeft(2, "0");
    }
  ).join(" ");
}

////////////////////////////////////////
//  nmxact/xact/image.go
//  Converted from Go: https://github.com/lupyuen/mynewt-newtmgr/blob/master/nmxact/xact/image.go

//////////////////////////////////////////////////////////////////////////////
// $state read                                                              //
//////////////////////////////////////////////////////////////////////////////

class ImageStateReadCmd 
  with CmdBase 
  implements Cmd 
{
  CmdBase base;

  //  TODO: ImageStateReadCmd(this.base);

  Result Run(
    Sesn s  //  Previously sesn.Sesn
  ) {
    final r = NewImageStateReadReq();  //  Previously nmp.NewImageStateReadReq()

    final rsp = txReq(s, r.Msg(), this.base);
    
    //  TODO: final srsp = rsp.ImageStateRsp;  //  Previously nmp.ImageStateRsp

    var res = newImageStateReadResult();
    //  TODO: res.Rsp = srsp;
    return res;
  }
}

class ImageStateReadResult implements Result {
  ImageStateRsp Rsp;  //  Previously nmp.ImageStateRsp

  int Status() {
    return this.Rsp.Rc;
  }
}

ImageStateReadCmd NewImageStateReadCmd() {
  return ImageStateReadCmd(
    //  TODO: NewCmdBase()
  );
}

ImageStateReadResult newImageStateReadResult() {
  return ImageStateReadResult();
}

////////////////////////////////////////
//  nmxact/nmp/image.go
//  Converted from Go: https://github.com/lupyuen/mynewt-newtmgr/blob/master/nmxact/nmp/image.go

//////////////////////////////////////////////////////////////////////////////
// $state                                                                   //
//////////////////////////////////////////////////////////////////////////////

/* TODO
  type SplitStatus int

  const (
    NOT_APPLICABLE SplitStatus = iota
    NOT_MATCHING
    MATCHING
  )

  //  returns the enum as a string
  func (sm SplitStatus) String() string {
    names := map[SplitStatus]string{
      NOT_APPLICABLE: "N/A",
      NOT_MATCHING:   "non-matching",
      MATCHING:       "matching",
    }

    str := names[sm]
    if str == "" {
      return "Unknown!"
    }
    return str
  }

  type ImageStateEntry struct {
    NmpBase
    Image     int    `codec:"image"`
    Slot      int    `codec:"slot"`
    Version   string `codec:"version"`
    Hash      []byte `codec:"hash"`
    Bootable  bool   `codec:"bootable"`
    Pending   bool   `codec:"pending"`
    Confirmed bool   `codec:"confirmed"`
    Active    bool   `codec:"active"`
    Permanent bool   `codec:"permanent"`
  }
*/

class ImageStateReadReq 
  with NmpBase       //  Get and set SMP Message Header
  implements NmpReq  //  SMP Request Message  
{
  NmpBase base;  //  Will not be encoded: `codec:"-"`

  NmpMsg Msg() { return MsgFromReq(this); }

  /// Encode the SMP Request fields to CBOR
  void Encode(cbor.MapBuilder builder) {
    // Add some map entries to the list.
    // Entries are added as a key followed by a value, this ordering is enforced.
    // Map keys can be integers or strings only, this is also enforced.
    // mapBuilder.writeString('a');   // key
    // mapBuilder.writeURI('a/ur1');  // value
    // mapBuilder.writeString('b');      // key
    // mapBuilder.writeEpoch(1234567899);// value
    // mapBuilder.writeString('c');           // key
    // mapBuilder.writeDateTime('19/04/2020');// value

    // Get our built map output and add it to the encoding stream.
    // The key/value pairs must be balanced, i.e. you must end the map building with
    // a value else the getData method will throw an exception.
    // Use the addBuilderOutput method to add built output to the encoder.
    // You can use the addBuilderOutput method on the map builder to add
    // the output of other list or map builders to its encoding stream.

    //  encoder.writeArray(<int>[1, 2, 3]);
    //  encoder.writeFloat(67.89);
    //  encoder.writeInt(10);
  }
}

/* TODO
  type ImageStateWriteReq struct {
    NmpBase `codec:"-"`
    Hash    []byte `codec:"hash"`
    Confirm bool   `codec:"confirm"`
  }

  type ImageStateRsp struct {
    NmpBase
    Rc          int               `codec:"rc"`
    Images      []ImageStateEntry `codec:"images"`
    SplitStatus SplitStatus       `codec:"splitStatus"`
  }
*/

ImageStateReadReq NewImageStateReadReq() {
  var r = ImageStateReadReq();
  fillNmpReq(r, NMP_OP_READ, NMP_GROUP_IMAGE, NMP_ID_IMAGE_STATE);
  return r;
}

/* TODO
  func NewImageStateWriteReq() *ImageStateWriteReq {
    r := &ImageStateWriteReq{}
    fillNmpReq(r, NMP_OP_WRITE, NMP_GROUP_IMAGE, NMP_ID_IMAGE_STATE)
    return r
  }

  func (r *ImageStateWriteReq) Msg() *NmpMsg { return MsgFromReq(r) }

  func NewImageStateRsp() *ImageStateRsp {
    return &ImageStateRsp{}
  }

  func (r *ImageStateRsp) Msg() *NmpMsg { return MsgFromReq(r) }
*/

////////////////////////////////////////
//  nmxact/nmxutil/nmxutil.go
//  Converted from Go: https://github.com/lupyuen/mynewt-newtmgr/blob/master/nmxact/nmxutil/nmxutil.go

int nextNmpSeq = 0;  //  Previously uint8
bool nmpSeqBeenRead = false;

/// Return the next SMP Message Sequence Number, 0 to 255. The first number is random.
int NextNmpSeq() {  //  Returns uint8
  //  TODO: seqMutex.Lock()
  //  TODO: defer seqMutex.Unlock()

  if (!nmpSeqBeenRead) {
    //  First number is random
    var rng = new Random();
    nextNmpSeq = rng.nextInt(256);  //  Returns 0 to 255
    nmpSeqBeenRead = true;
  }

  final val = nextNmpSeq;
  nextNmpSeq = (nextNmpSeq + 1) % 256;
  assert(val >= 0 && val <= 255);
  return val;
}

////////////////////////////////////////
//  nmxact/xact/cmd.go
//  Converted from Go: https://github.com/lupyuen/mynewt-newtmgr/blob/master/nmxact/xact/cmd.go

/// Result of an SMP operation
abstract class Result {
	int Status();
}

/// SMP Command
abstract class Cmd {
	/// Transmits request and listens for response; blocking.
	Result Run(Sesn s);                //  Previously sesn.Sesn
	void Abort();

	//  TxOptions TxOptions();             //  Previously sesn.TxOptions
	void SetTxOptions(TxOptions opt);  //  Previously sesn.TxOptions
}

/// Base Class for SMP Command
mixin CmdBase {
	TxOptions txOptions;  //  Previously sesn.TxOptions
	int curNmpSeq;        //  Previously uint8
	Sesn curSesn;         //  Previously sesn.Sesn
	Exception abortErr;   // Previously error

  /// Constructor
  //  CmdBase(this.txOptions);

  /*
  TxOptions TxOptions() {  //  Previously sesn.TxOptions
    return this.txOptions;
  }
  */

  void SetTxOptions(
    TxOptions opt  //  Previously sesn.TxOptions
  ) {
    this.txOptions = opt;
  }

  void Abort() {
    if (this.curSesn != null) {
        //  TODO: this.curSesn.AbortRx(this.curNmpSeq);
    }
    this.abortErr = Exception("Command aborted");
  }
}

/*
CmdBase NewCmdBase() {
	return CmdBase(
		NewTxOptions()  //  Previously sesn.NewTxOptions
  );
}
*/

////////////////////////////////////////
//  nmxact/xact/xact.go
//  Converted from Go: https://github.com/lupyuen/mynewt-newtmgr/blob/master/nmxact/xact/xact.go

/// Transmit an SMP Request and get the SMP Response
NmpRsp txReq(  //  Returns nmp.NmpRsp
  Sesn s,      //  Previously sesn.Sesn
  NmpMsg m,    //  Previously nmp.NmpMsg
  CmdBase c
) {
  //  TODO: assert(c != null);
  if (c != null) {  //  TODO: Should not be null
    if (c.abortErr != null) {
      throw c.abortErr;
    }
    c.curNmpSeq = m.Hdr.Seq;
    c.curSesn = s;
  }

	//  TODO: final rsp = sesn.TxRxMgmt(s, m, c.TxOptions());
  final rsp = ImageStateRsp();
  final data = EncodeNmpPlain(m);

  if (c != null) {  //  TODO: Should not be null
    c.curNmpSeq = 0;
    c.curSesn = null;
  }
	return rsp;
}

////////////////////////////////////////
//  nmxact/nmp/defs.go
//  Converted from Go: https://github.com/lupyuen/mynewt-newtmgr/blob/master/nmxact/nmp/defs.go

const
  NMP_OP_READ      = 0,
  NMP_OP_READ_RSP  = 1,
  NMP_OP_WRITE     = 2,
  NMP_OP_WRITE_RSP = 3;

const
  NMP_ERR_OK       = 0,
  NMP_ERR_EUNKNOWN = 1,
  NMP_ERR_ENOMEM   = 2,
  NMP_ERR_EINVAL   = 3,
  NMP_ERR_ETIMEOUT = 4,
  NMP_ERR_ENOENT   = 5;

// First 64 groups are reserved for system level newtmgr commands.
// Per-user commands are then defined after group 64.

const
  NMP_GROUP_DEFAULT = 0,
  NMP_GROUP_IMAGE   = 1,
  NMP_GROUP_STAT    = 2,
  NMP_GROUP_CONFIG  = 3,
  NMP_GROUP_LOG     = 4,
  NMP_GROUP_CRASH   = 5,
  NMP_GROUP_SPLIT   = 6,
  NMP_GROUP_RUN     = 7,
  NMP_GROUP_FS      = 8,
  NMP_GROUP_SHELL   = 9,
  NMP_GROUP_PERUSER = 64;

// Default group (0).
const
  NMP_ID_DEF_ECHO           = 0,
  NMP_ID_DEF_CONS_ECHO_CTRL = 1,
  NMP_ID_DEF_TASKSTAT       = 2,
  NMP_ID_DEF_MPSTAT         = 3,
  NMP_ID_DEF_DATETIME_STR   = 4,
  NMP_ID_DEF_RESET          = 5;

// Image group (1).
const
  NMP_ID_IMAGE_STATE    = 0,
  NMP_ID_IMAGE_UPLOAD   = 1,
  NMP_ID_IMAGE_CORELIST = 3,
  NMP_ID_IMAGE_CORELOAD = 4,
  NMP_ID_IMAGE_ERASE    = 5;

// Stat group (2).
const
  NMP_ID_STAT_READ = 0,
  NMP_ID_STAT_LIST = 1;

// Config group (3).
const
  NMP_ID_CONFIG_VAL = 0;

// Log group (4).
const
  NMP_ID_LOG_SHOW        = 0,
  NMP_ID_LOG_CLEAR       = 1,
  NMP_ID_LOG_APPEND      = 2,
  NMP_ID_LOG_MODULE_LIST = 3,
  NMP_ID_LOG_LEVEL_LIST  = 4,
  NMP_ID_LOG_LIST        = 5;

// Crash group (5).
const
  NMP_ID_CRASH_TRIGGER = 0;

// Run group (7).
const
  NMP_ID_RUN_TEST = 0,
  NMP_ID_RUN_LIST = 1;

// File system group (8).
const
  NMP_ID_FS_FILE = 0;

// Shell group (8).
const
  NMP_ID_SHELL_EXEC = 0;

////////////////////////////////////////
//  TODO: Check response from PineTime

class ImageStateRsp 
  implements NmpRsp
{
  //  TODO
  int Rc;
  //  TODO
  NmpHdr Hdr() { return NmpHdr(0, 0, 0, 0, 0, 0); }
  //  TODO
  NmpMsg Msg() { return NmpMsg(null, null); }
  //  TODO
  void SetHdr(NmpHdr msg) {}
}

/// Bluetooth LE Session
class Sesn {}

Sesn GetSesn() {
  return Sesn();
}

/// Bluetooth LE Transmission Options
class TxOptions {}

TxOptions NewTxOptions() {
  return TxOptions();
}

////////////////////////////////////////
//  Send Simple Mgmt Protocol Command to PineTime over Bluetooth LE

void main() {
  composeRequest();
}

/// Compose a request to query firmware images on PineTime
typed.Uint8Buffer composeRequest() {
  //  Create the SMP Request
  final req = NewImageStateReadReq();

  //  Encode the SMP Message with CBOR
  final msg = req.Msg();
  final data = EncodeNmpPlain(msg);
  return data;
}

/// Query firmware images on PineTime
void testCommand() {
  //  Fetch the Bluetooth LE Session
  final s = GetSesn();

  //  Create the SMP Command
  final c = NewImageStateReadCmd();  //  Previously xact.NewImageStateReadCmd()

  //  TODO: Set the Bluetooth LE transmission options
  //  c.SetTxOptions(nmutil.TxOptions());

  //  Transmit the SMP Command
  final res = c.Run(s);

  //  TODO: Handle SMP Response
  //  final ires = res.ImageStateReadResult;  //  Previously xact.ImageStateReadResult
  //  imageStatePrintRsp(ires.Rsp);
}

/// Test the CBOR library for Encoding. Based on https://github.com/shamblett/cbor/blob/master/example/cbor_map_builder.dart
void testCborEncoding() {
  /// An example of using the Map Builder class.
  /// Map builder is used to build maps with complex values such as tag values, indefinite sequences
  /// and the output of other list or map builders.

  // Get our cbor instance, always do this,it correctly
  // initialises the decoder.
  final inst = cbor.Cbor();

  // Get our encoder
  final encoder = inst.encoder;

  // Encode some values
  encoder.writeArray(<int>[1, 2, 3]);
  encoder.writeFloat(67.89);
  encoder.writeInt(10);

  // Get our map builder
  final mapBuilder = cbor.MapBuilder
    .builder();

  // Add some map entries to the list.
  // Entries are added as a key followed by a value, this ordering is enforced.
  // Map keys can be integers or strings only, this is also enforced.
  mapBuilder.writeString('a'); // key
  mapBuilder.writeURI('a/ur1');
  mapBuilder.writeString('b'); // key
  mapBuilder.writeEpoch(1234567899);
  mapBuilder.writeString('c'); // key
  mapBuilder.writeDateTime('19/04/2020');

  // Get our built map output and add it to the encoding stream.
  // The key/value pairs must be balanced, i.e. you must end the map building with
  // a value else the getData method will throw an exception.
  // Use the addBuilderOutput method to add built output to the encoder.
  // You can use the addBuilderOutput method on the map builder to add
  // the output of other list or map builders to its encoding stream.
  final mapData = mapBuilder
    .getData();
  encoder
    .addBuilderOutput(mapData);

  // Add another value
  encoder.writeRegEx('^[12]g');

  // Decode ourselves and pretty print it.
  inst.decodeFromInput();
  print(inst.decodedPrettyPrint(false));

  // Finally to JSON
  print(inst.decodedToJSON());

  // JSON output is :-
  // [1,2,3],67.89,10,{"a":"a/ur1","b":1234567899,"c":"19/04/2020"},"^[12]g"

  //  Get the encoded body
  final data = inst.output.getData();
  print("Encoded ${ inst.decodedToJSON() } to:\n${ hexDump(data) }");
}

/// Test the CBOR library for Decoding. Based on https://github.com/shamblett/cbor/blob/master/example/cbor_payload_decode.dart
void testCborDecoding() {
  /// An example of using the Map Builder class.
  /// Map builder is used to build maps with complex values such as tag values, indefinite sequences
  /// and the output of other list or map builders.

  final payload = <int>[
    0xbf, 0x66, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, // |.fimages|
    0x9f, 0xbf, 0x64, 0x73, 0x6c, 0x6f, 0x74, 0x00, 0x67, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // |..dslot.gversion|
    0x65, 0x31, 0x2e, 0x30, 0x2e, 0x30, 0x64, 0x68, 0x61, 0x73, 0x68, 0x58, 0x20, 0x70, 0x3e, 0xbb, // |e1.0.0dhashX, 0xp>.|
    0xf8, 0x11, 0x45, 0x8b, 0x1f, 0xad, 0x18, 0x9e, 0x64, 0xe3, 0xa5, 0xe0, 0xf8, 0x09, 0xcb, 0xe6, // |..E.....d.......|
    0xba, 0xd8, 0x83, 0xc7, 0x6b, 0x3d, 0xd7, 0x12, 0x79, 0x1c, 0x82, 0x2f, 0xb5, 0x68, 0x62, 0x6f, // |....k=..y../.hbo|
    0x6f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0xf5, 0x67, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0xf4, // |otable.gpending.|
    0x69, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0xf5, 0x66, 0x61, 0x63, 0x74, 0x69, // |iconfirmed.facti|
    0x76, 0x65, 0xf5, 0x69, 0x70, 0x65, 0x72, 0x6d, 0x61, 0x6e, 0x65, 0x6e, 0x74, 0xf4, 0xff, 0xff, // |ve.ipermanent...|
    0x6b, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x00, 0xff              // |ksplitStatus..| 
  ];

  // Get our cbor instance, always do this,it correctly
  // initialises the decoder.
  final inst = cbor.Cbor();

  final payloadBuffer = typed.Uint8Buffer();
  payloadBuffer.addAll(payload);

  // Decode from the buffer, you can also decode from the
  // int list if you prefer.
  inst.decodeFromBuffer(payloadBuffer);

  // Pretty print, note that these methods use [GetDecodedData] and will
  // thus build the payload buffer.
  // If you do not want to pretty print or use Json just get the list of
  // decoded data directly by calling [GetDecodedData()]
  print(inst.decodedPrettyPrint());

  // JSON, maps can only have string keys to decode to JSON
  print(inst.decodedToJSON());

  // Print CBOR and JSON sizes
  print(
    "CBOR size: ${payload.length} bytes\n"
    "JSON size: ${inst.decodedToJSON().length} bytes\n"
  );
}